<?php
/**
* This file belongs to a webappkit package (kit)
* @link http://webappkit.net
* @package datapath
* @author J.Ducastel <jeremie@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

/**
* accessing multidimensional data (object vars or array) through path syntax.
* <p>this is a static method, works on data structures by reference</p>
* <p>default syntax is "dot" i.e. some.path.to.data = $data['some']['path']['to']['data']</p>
* @static
* @author J.Ducastel <jeremie@ducastel.name>
*/
class dataPath {
/*--------------------------------------------------------------------
PUBLIC STATIC METHODS
--------------------------------------------------------------------*/
	/**
	* set or get path segments separator character.
	* @static
	* @param string $new optional, single character, new separator
	* @access public
	* @return string
	*/
	function separator($new=null) {
		static $separator='.';
		if (is_string($new) and strlen($new)==1)
			$separator=$new;
		return $separator;
	}/**/
	
	/**
	* get targeted path from data
	* @static
	* @param mixed &$data array or object
	* @param string $path
	* @access public
	* @return mixed targeted path, by reference
	*/
	function & get(&$data, $path=null) {
		if (is_null($path)
		or (is_string($path) and strlen($path)==0))
			// path is blank, returning root
			return $data;
		// parsing path
		$childkey=datapath::cutpath($path);
		// reaching target
		// getting branch
		$child=&datapath::getchild($data,$childkey);
		if (!$child)
			// branch doesn't exist
			return $child;
		if (strlen($path)==0)
			// branch exist, end of path
			return $child;
		// branch exist,path continue
		$target=&datapath::get($child,$path);
		//echo '<p>datapath::get '.$path.' was '.wakapi::vardump($target).'</p>';
		return $target;
	}/**/
	
	/**
	* sets targeted path value.
	* overwrite existing members if needed
	* creates members if needed
	* @static
	* @param array|object &$data
	* @param mixed $value
	* @param string $path
	* @access public
	* @return bool
	*/
	function set(&$data,$value,$path=null) {
		//echo '<p>setting '.$path.' as '.print_r($value).'</p>';
		// parsing path
		if (!$branch=datapath::cutpath($path)) {
			// setting root
			$data=$value;
			return true;
		}
		// is target reached ?
		if (strlen($path)==0) {
			return datapath::setchild($data,$branch,$value);
		}
		// continue along the path
		$child=&datapath::getchild($data,$branch);
		//if (!is_array($child) or !is_object($child))
		if (!$child)
			$child=&datapath::makechild($data,$branch);
		return datapath::set($child,$value,$path);
	}/**/
	
	/**
	* sets a key by reference 
	* @static
	* @param array &$data source data
	* @param mixed &$ref reference
	* @param string $path
	* @access public
	* @return bool
	*/
	function setRef(&$data,&$ref,$path=null) {
		//echo "<p>setting ref on $path</p>";
		// is root ?
		if (is_null($path)
		or is_string($path) and strlen($path)==0) {
			$data=&$ref;
			return true;
		}
		// parsing path : substracting next branch from path
		if (!$branch=datapath::cutpath($path)) {
			return false;
		}
		// is target reached ?
		if (strlen($path)==0) {
			//echo "<p>target reached : $branch</p>";
			return datapath::setchild($data,$branch,$ref);
		}
		// continue along the path
		//echo "<p>continuing on $branch</p>";
		// getting or creating the child at branch
		if (!$child=&datapath::getchild($data,$branch))
			$child=&datapath::makechild($data,$branch);
		// print_r($child);
		return datapath::setref($child,$ref,$path);
	}/**/
	
	/**
	* return child keys for a path if available
	* @static
	* @param array &$data
	* @param string $path
	* @access public
	* @return array|false
	*/
	function getChildKeys(&$data,$path=null) {
		if (!$target=&datapath::get($data,$path))
			return false;
		switch (true) {
			case is_array($target):
				return array_keys($target);
			case is_object($target):
				return array_keys(get_object_vars($target));
			default:
				return false;
		}
	}/**/
	
	/**
	* loops through path
	* @param array|object &$data
	* @param string $path
	* @access public
	* @return mixed
	* @todo
	* /
	function loop(&$data, $path) {
		static $looping=false;
		static $keys=array();
		static $ifni
	}/**/
	
	/**
	* Merges recursiveley two data structures
	* @static
	* @param array $data1
	* @param array $data2
	* @access public
	* @return array
	*/
	function & merge($data1, $data2) {
		// will be the merged data
		$merged=$data1;
		// getting keys to compare
		$keys1=datapath::getchildkeys($data1);
		//print_r($keys1);
		// looping through first keys
		foreach ($keys1 as $key) {
			// is the key present in both data structs ?
			if (!datapath::getchild($data2,$key))
				// only in data1, passing
				continue;
			// getting values to compare
			$value1=datapath::get($data1,$key);
			$value2=datapath::getchild($data2,$key);
			// are both values arrays ?
			if (is_array($value1) and is_array($value2)) {
				// recursion
				$newvalue=datapath::merge($value1,$value2);
			} else {
				$newvalue=$value2;
			}
			datapath::set($merged,$newvalue,$key);
		}
		/*foreach ($merged as $key => $value) {
			if (!isset($array_2[$key]))
				continue;
			if (is_array($value) and is_array($array_2[$key])) {
				// appel recursif
				$merged[$key]=$this->array_merge_recursive_replace($value,$array_2[$key]);
			} else {
				$merged[$key]=$array_2[$key];
			}
		}*/
		// adding new keys : looping through data2
		$keys2=datapath::getchildkeys($data2); //print_r($keys2);
		foreach ($keys2 as $key) {
			if (!datapath::getchild($merged,$key))
				datapath::set($merged,datapath::get($data2,$key),$key);
		}
		/*foreach ($array_2 as $key => $value)
			if (!isset($merge[$key])) $merge[$key]=$value;*/
		return $merged;
	}/**/

/*--------------------------------------------------------------------
for self use
--------------------------------------------------------------------*/
	/**
	* remove next path segment from passed path, and returns it
	* @static
	* @param string &$path
	* @access protected
	* @return string next segment
	*/
	function cutPath(&$path) {
		if (!is_string($path))
			return false;
		$oldpath=$path;
		$sep=datapath::separator();
		// parsing path, separating current step from next one(s)
		$steps=explode($sep,$path); //var_dump($steps) ;
		$segment=array_shift($steps);
		$path=join($sep,$steps);
		// echo '<p>cutted '.$oldpath.' to '.$path.'</p>';
		return $segment;
	}/**/
	
	/**
	* return data member if exists
	* @static
	* @param array|object &$data source data
	* @param string $key
	* @access protected
	* @return mixed
	*/
	function & getChild(&$data,$key) {
		switch (true) {
			/*case !is_string($key):
				trigger_error("key $key is not valid");
				break;*/
			// array, getting member
			case is_array($data) and isset($data[$key]):
				$child=&$data[$key];
				break;
			// object, getting member directly
			case is_object($data) and isset($data->$key):
				// echo "$key from object";
				$child=&$data->$key;
				break;
			// object, getting method result
			case is_object($data) and method_exists($data,$key):
				$child=$data->$key();
				break;
			default:
				$child=null;
		}
		return $child;
	}/**/
	
	/**
	* sets data member
	* @static
	* @param array|object &$data source data
	* @param string $key
	* @param mixed $value
	* @access protected
	* @return bool
	*/
	function setChild(&$data,$key, &$value) {
		switch (true) {
			// array, setting member
			case is_array($data):
				$data[$key]=&$value;
				return true;
				break;
			// object, accessing member
			case is_object($data) and isset($data->$key):
				$data->$key=&$value;
				return true;
			// object, member not set
			case is_object($data):
				$data->$key=&$value;
				return true;
			default:
				// trigger_error("can't set child $key as data is scalar");
				// making it array
				$data=array();
				$data[$key]=&$value;
				return false;
		}
		// return $child;
	}/**/
	
	/**
	* creates data member and return its reference
	* @static
	* @param array|object &$data source data
	* @param string $key
	* @param mixed $value
	* @access protected
	* @return bool
	*/
	function & makeChild(&$data,$key) {
		//echo "before :";
		//var_dump($data);
		switch (true) {
			// array, setting member
			case is_array($data):
				// target is an array
				$data[$key]=true;
				$child=&$data[$key];
				break;
			// object, setting member
			case is_object($data) and isset($data->$key):
				$data->$key=true;
				$child=&$data->$key;
				break;
			// object, method
			/*case is_object($data) and method_exists($data,$key):
				echo "<p>method $key from object</p>";
				$child=$data->$key($value);
				//return true;
				break;*/
			default:
				// making it array
				//echo "data was scalar : ".wakdebug::vardump($data);
				$data=array();
				$data[$key]=true;
				$child=&$data[$key];
				break;
				//$child=array();
				//$data=array($key=>true);
				//$child=&$data[$key];
				//$newdata=array($key=>&$child);
				//$data=&$newdata;
				//$data[$key]=&$child;
		}
		//echo "after :";
		//var_dump($data);
		return $child;
	}/**/
	
	/**
	* checks path validity
	* @static
	* @param string $path
	* @access protected
	* @return bool
	*/
	function checkPath($path) {
		return is_string($path);
	}/**/
}
?>
